# -*- coding: utf-8 -*-
# @Author: Jerry
# @Date:   2022-03-09 20:07:46
# @Last Modified by:   Jerry
# @Last Modified time: 2022-03-10 12:02:05

# HPF = high pass filter，高通滤波器
# LPF = low  pass filter，低通滤波器

# **********
# OpenCV中的图像处理 » 4_11_傅里叶变换
# http://www.woshicver.com

# **********
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import os
rootpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
datapath = os.path.join(rootpath,'data')
imgpath = lambda name: os.path.join(datapath,name)


# **********
'''目标
本节学习
- 使用OpenCV查找图像的傅立叶变换
- 利用Numpy中可用的FFT函数
- 傅立叶变换的某些应用程序
- 我们将看到以下函数：cv.dft()，cv.idft()等'''

# **********
# 傅里叶变换和逆傅里叶变换，让用户可以对图像的频域图做一些处理
# 1.傅里叶变换 -> 得到频域图谱
# 2.对频域图谱进行图形处理，比如遮罩
# 3.对处理后的 频域图谱 进行逆傅里叶变换，得到处理后的原图

# **********
'''理论
傅立叶变换用于分析各种滤波器的频率特性。对于图像，使用 2D离散傅里叶变换(DFT)查找频域。
一种称为 快速傅立叶变换(FFT) 的快速算法用于DFT的计算。
关于这些的详细信息可以在任何图像处理或信号处理教科书中找到。

对于正弦信号x(t) = A*sin(2*pi*f*t)，我们可以说f是信号的频率，如果采用其频域，则可以看到f的尖峰。
如果对信号进行采样以形成离散信号，我们将获得相同的频域，但是在[-π，π]或[0,2π]范围内（对于N点DFT为[0，N]）是周期性的。
您可以将图像视为在两个方向上采样的信号。因此，在X和Y方向都进行傅立叶变换，可以得到图像的频率表示。

更直观地说，对于正弦信号，如果幅度在短时间内变化如此之快，则可以说它是高频信号。如果变化缓慢，则为低频信号。
您可以将相同的想法扩展到图像。图像中的振幅在哪里急剧变化？在边缘点或噪声。
因此，可以说边缘和噪声是图像中的高频内容。如果幅度没有太大变化，则它是低频分量。（一些链接已添加到“其他资源”，其中通过示例直观地说明了频率变换）。'''


# **********
'''Numpy中的傅里叶变换:
        Numpy具有FFT软件包来执行此操作
np.fft.fft2()为我们提供了频率转换,
- 第一个参数是输入图像，即灰度图像。
- 第二个参数是可选的，它决定输出数组的大小。
  如果它大于输入图像的大小，则在计算FFT之前用零填充输入图像。
  如果小于输入图像，将裁切输入图像。
  如果未传递任何参数，则输出数组的大小将与输入的大小相同。
- 经过傅里叶变换的原始频域图谱中，零频率分量（DC分量）将位于四个角上。
  如果要使其居中，则需要在两个方向上将结果都移动N/2.
  函数np.fft.fftshift()即完成此功能：即以中心点为坐标原点，每个象限的内容都坐下-右上翻转'''

# img = cv.imread(imgpath('lena.jpg'),0)
img = cv.imread(imgpath('messi5.jpg'),0)
f = np.fft.fft2(img)
# magnitude_spectrum = 20*np.log(np.abs(f))
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20*np.log(np.abs(fshift))
# ----------
# plt.subplot(121),plt.imshow(img, cmap = 'gray')
# plt.title('Input Image'), plt.xticks([]), plt.yticks([])
# plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
# plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
# plt.show()
# ----------
# 铜鼓这个messi5.jpg图像的频谱图观察和以上的理论介绍可知，
# 频率分量的值越低，频谱图上所在像素的灰度值越大，越接近白色。
# 经过以上转化后，观察可知频谱图中心位置发白的部分为低频分量，这一区域白色越多越表明低频内容多


'''
因此，您发现了频率变换。现在，您可以在频域中进行一些操作，例如高通滤波和重建图像，即找到逆DFT。
为此，您只需用尺寸为60x60的矩形窗口遮罩即可消除低频。遮罩去除，即进行and运算即可
然后，使用np.fft.ifftshift()应用反向移位，以使DC分量再次出现在左上角。
然后使用np.ifft2()函数找到逆FFT。
同样，结果将是一个复数。
您可以采用其绝对值。
'''
rows, cols = img.shape
crow,ccol = rows//2 , cols//2 # center of row and col
fshift[crow-30:crow+31, ccol-30:ccol+31] = 0 # 此处没有用and屏蔽值，而是直接赋值0，简单粗暴
f_ishift = np.fft.ifftshift(fshift) # 反向移位
img_back = np.fft.ifft2(f_ishift) # 逆傅里叶变换
img_back = np.real(img_back) # 复数处理
# ----------
# plt.subplot(131),plt.imshow(img, cmap = 'gray') # 原图的灰度图显示
# plt.title('Input Image'), plt.xticks([]), plt.yticks([])
# plt.subplot(132),plt.imshow(img_back, cmap = 'gray') # 遮罩处理后的恢复得到的灰度图
# plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
# plt.subplot(133),plt.imshow(img_back) # 遮罩处理后的恢复得到的原图
# plt.title('Result in JET'), plt.xticks([]), plt.yticks([])
# plt.show()
# ----------
# 结果表明高通滤波是边缘检测操作。这就是我们在“图像渐变”一章中看到的。
# 这也表明大多数图像数据都存在于频谱的低频区域。


# **********
'''OpenCV中的傅里叶变换
OpenCV为此提供了**cv.dft**()和**cv.idft**()函数。
它返回与前一个相同的结果，但是有两个通道。
第一个通道是结果的实部，第二个通道是结果的虚部。
输入图像首先应转换为np.float32'''
img = cv.imread(imgpath('messi5.jpg'),0)
dft = cv.dft(np.float32(img),flags = cv.DFT_COMPLEX_OUTPUT) # 傅里叶变换
dft_shift = np.fft.fftshift(dft) # 移位, 得到的就是频谱图数据
magnitude_spectrum = 20*np.log(cv.magnitude(dft_shift[:,:,0],dft_shift[:,:,1])) # 实部、虚部
# magnitude：[ˈmæɡnɪtuːd] n. 巨大，重要性；震级；规模，大小；数量，数值；（恒星的）亮度，星等
# spectrum：[ˈspektrəm] n. 范围，幅度；光谱；波谱，频谱；余象
# ----------
# plt.subplot(121),plt.imshow(img, cmap = 'gray') # 原图的灰度图
# plt.title('Input Image'), plt.xticks([]), plt.yticks([])
# plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray') # 星形频谱图的灰度图
# plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
# plt.show()
# ----------

# 注意 您还可以使用**cv.cartToPolar**()，它在单个镜头中同时返回幅值和相位
# 现在我们要做DFT的逆变换。
# 在上一节中，我们创建了一个HPF，这次我们将看到如何删除图像中的高频内容，即我们将LPF应用到图像中。
# 它实际上模糊了图像。为此，我们首先创建一个高值(1)在低频部分，即我们过滤低频内容，0在高频区。
rows, cols = img.shape
crow,ccol = rows/2 , cols/2
# 首先创建一个掩码，中心正方形为1，其余全为零
mask = np.zeros((rows,cols,2),np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1 # 建立一个低通滤波器
# 应用掩码和逆DFT
fshift = dft_shift*mask # 低通滤波器LPF作用于原图的频谱图
f_ishift = np.fft.ifftshift(fshift) # 反向移位
img_back = cv.idft(f_ishift) # 逆傅里叶变换
img_back = cv.magnitude(img_back[:,:,0],img_back[:,:,1]) #实部、虚部结合起来才是一个图的数据结构
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image 1'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Magnitude Spectrum 1'), plt.xticks([]), plt.yticks([])
plt.show()


# **********
'''为什么拉普拉斯算子是高通滤波器？
在一个论坛上也有人提出了类似的问题。问题是，为什么拉普拉斯变换是高通滤波器?为什么Sobel是HPF?等。
第一个答案是关于傅里叶变换的。
对于更大的FFT只需要拉普拉斯变换。
分析下面的代码：'''

# 没有缩放参数的简单均值滤波器
mean_filter = np.ones((3,3))
# 创建高斯滤波器
x = cv.getGaussianKernel(5,10)
gaussian = x*x.T
# 不同的边缘检测滤波器
# x方向上的scharr
scharr = np.array([[-3, 0, 3],
                   [-10,0,10],
                   [-3, 0, 3]])
# x方向上的sobel
sobel_x= np.array([[-1, 0, 1],
                   [-2, 0, 2],
                   [-1, 0, 1]])
# y方向上的sobel
sobel_y= np.array([[-1,-2,-1],
                   [0, 0, 0],
                   [1, 2, 1]])
# 拉普拉斯变换
laplacian=np.array([[0, 1, 0],
                    [1,-4, 1],
                    [0, 1, 0]])
filters = [mean_filter, gaussian, laplacian, sobel_x, sobel_y, scharr]
filter_name = ['mean_filter', 'gaussian','laplacian', 'sobel_x', \
                'sobel_y', 'scharr_x']
fft_filters = [np.fft.fft2(x) for x in filters]
fft_shift = [np.fft.fftshift(y) for y in fft_filters]
mag_spectrum = [np.log(np.abs(z)+1) for z in fft_shift]
# ----------
# for i in xrange(6):
#     plt.subplot(4,3,i+1),plt.imshow(mag_spectrum[i],cmap = 'gray')
#     plt.title(filter_name[i]), plt.xticks([]), plt.yticks([])
# plt.show()
# ----------
# 从图像中，您可以看到每种内核阻止的频率区域以及它允许经过的区域。
# 从这些信息中，我们可以说出为什么每个内核都是HPF或LPF
#



